import py7zr
import os
import os.path
import hashlib
import exceptions
import objectStateConstants
import json
from xml.etree.ElementTree import *

debug = objectStateConstants.ObjectStateConstant()  # 项目数据加载


# debug.dp("a")

# import shutil


def getHash(inp: str):
    """
    This function can return the sha256 Value in hex digest.
    :param inp: input str
    :return: the sha256 value
    """
    return hashlib.sha256(inp.encode('utf-8')).hexdigest()


def encryption(formatR, sign):
    """
    This function can make the ASTF file correct format.
    # encryption rules
    # Original format:O_f  Original compressed file of 7z
    # signed: str: S
    # signed + O_f + hash(S + hash(hash(S + hash(S)) + S) + S)

    :param formatR: Original compressed file of 7z
    :param sign: signed
    :return: ASTF file in bin
    """
    part1 = getHash(sign)
    part2 = getHash(sign + part1)
    part3 = getHash(part2 + sign)
    part4 = getHash(sign + part3 + sign)
    return sign.encode('utf-8') + formatR + part4.encode('utf-8')


def checkEncryption(sign):
    """
    This function can return Caudal infill.
    :param sign: signed
    :return:
    # signed: str: S
    # hash(S + hash(hash(S + hash(S)) + S) + S)
    """
    part1 = getHash(sign)
    part2 = getHash(sign + part1)
    part3 = getHash(part2 + sign)
    part4 = getHash(sign + part3 + sign)
    return part4


def decompress(file, sign, password=None):
    """
    decompress function.
    :param file: file path
    :param sign: signed
    :param password: password,
    if you set password, fill in the blanks
    if not, just keep it None
    :return: Nothing
    """
    with open(file, 'rb') as f:
        signed = f.read()
    if signed[:sign.encode("utf-8").__len__()] == sign.encode('utf-8'):
        print("sign length correct")
        if signed[signed.__len__() - 64:] == checkEncryption(sign).encode('utf-8'):
            print("Caudal infill correct")
            if password is None:
                password = checkEncryption(sign)
            try:
                with open(file.replace(".astf", ".7z"), 'wb') as f:
                    debug.dp(signed[len(sign.encode("utf-8")):])
                    f.write(signed[len(sign.encode("utf-8")):])
                archive = py7zr.SevenZipFile(file.replace(".astf", ".7z"), password=password)
                archive.extractall(path="./tmp")
            except py7zr.Bad7zFile:
                print("ERROR: This 7zip file is corrupt.")
                print("Try to use the 7z application (7z.exe) to open this file. ")
                print("If it still fails, there may be a bug. Please give feedback in time!")
                raise py7zr.Bad7zFile
            except py7zr.exceptions.PasswordRequired:
                print("ERROR: Password error!")
                print("If you set password by yourself:")
                print("Please check whether your input is correct and the calling function is correct.")
                print("If you didn't change the password:")
                print("Try to verify that your signature is correct.")
                print("If you do not have a file signature, you cannot open this file.")
                raise py7zr.exceptions.PasswordRequired
        else:
            print("Warning: This ASTF file failed the signature check.")
            print("If you do not have a file signature, you cannot open this file.")
            raise exceptions.SignatureError
    else:
        print("Warning: This ASTF file failed the signature check.")
        print("If you do not have a file signature, you cannot open this file.")
        raise exceptions.SignatureError


def compress(file, output, sign, password=None):
    """
    compress, generate the ASTF file
    :param file: Path to the folder to be compressed
    :param output: Path to the ASTF file output
    :param sign:signed
    :param password:password
    if you want to give your file a password, fill in the blanks, but this is not recommended
    if not, just keep it None, the programme will make a very strong password
    :return:Nothing
    """
    if password is None:
        password = checkEncryption(sign)
    with py7zr.SevenZipFile(output, mode='w', password=password) as zf:
        zf.writeall(file, arcname=os.path.basename(file))
    with open(output, 'rb') as f:
        signed = f.read()

    # encryption rules
    # Original format:O_f
    # signed: str: S
    # signed + O_f + hash(S + hash(hash(S + hash(S)) + S) + S)

    with open(output, 'wb') as f:
        f.write(encryption(signed, sign))
    debug.dp(signed)
    debug.dp(encryption(signed, sign))
    # print(signed)
    # print(encryption(signed, sign))
    # print(getHash("A").__len__())


class ASTF_Format:
    def __init__(self, path):
        decompress(path, "test")
        if os.listdir("./tmp").__len__() == 0:  # 判断文件包中是否啥都没有
            raise Exception("Empty archive")
        else:
            self.fileTrueName = os.listdir("./tmp")[0]  # 文件名(不含后缀)
            # example: Test
            self.file_folderList = os.listdir("./tmp/" + self.fileTrueName)  # 文件夹内置文件数据名称列表
            # example:
            # ['information.json', 'manifest.json', 'score_paperDef.xml', 'soundsDefinition.json', 'tracks',
            # 'tracksDefinition.json']
            debug.dp(self.file_folderList)

            self.fileJUD = self.File_structure_judgment()  # 文件结构判断返回一个内容列表
            # example:
            # {'mani': True, 'infor': True, 'paper': True, 'soundDef': True, 'trackDef': True,
            # 'tracksFileFolder': True}

            for i in self.fileJUD.values():  # 判断结构
                if i is not True:
                    raise exceptions.MissingFileContentError(i)

            # Interpretation of document content integrity
            # 判断文件结构完整性变量准备
            self.isManifestNoBug = False
            self.isSettingsNoBug = False
            self.isTracksNoBug = False

            self.manifestJsonDetector(self.manifestJsonGetter())  # 检验完整性

            # 文件设置信息变量准备
            self.scoreStyle = []
            self.detailInformation = {}
            self.soundsDef = {}
            self.tracksDef = {}

            self.SettingsGetter()

    def File_structure_judgment(self) -> dict:
        """
        ASTF文件结构判断函数
        A function that's used to detect the structure of the ASTF file.
        :return:返回一个字典数据，结构如下：
        {"mani": False,  --manifest.json是否存在
        "infor": False,   --information.json是否存在
        "paper": False,   --score_paperDef.xml是否存在
        "soundDef": False,   --soundsDefinition.json是否存在
        "trackDef": False,  --tracksDefinition.json是否存在
        "tracksFileFolder": False}  --tracks文件夹是否存在
        """
        if self.file_folderList.__len__() == 0:  # 判断文件包中是否啥都没有
            raise Exception("Empty archive")

        returnList = {"mani": False, "infor": False, "paper": False, "soundDef": False, "trackDef": False,
                      "tracksFileFolder": False}
        # 返回一个字典数据，结构如下：
        # {"mani": False,  --manifest.json是否存在
        # "infor": False,   --information.json是否存在
        # "paper": False,   --score_paperDef.xml是否存在
        # "soundDef": False,   --soundsDefinition.json是否存在
        # "trackDef": False,  --tracksDefinition.json是否存在
        # "tracksFileFolder": False}  --tracks文件夹是否存在
        for i in self.file_folderList:  # 判断
            if i == "manifest.json":
                returnList["mani"] = True
            if i == "information.json":
                returnList["infor"] = True
            if i == 'score_paperDef.xml':
                returnList["paper"] = True
            if i == 'soundsDefinition.json':
                returnList['soundDef'] = True
            if i == "tracksDefinition.json":
                returnList["trackDef"] = True
            if i == "tracks":
                returnList["tracksFileFolder"] = True
        return returnList

    def manifestJsonGetter(self) -> dict:
        """
        返回manifest.json的内容
        :return: the things in the manifest.json.
        """
        if self.fileJUD["mani"]:
            with open("./tmp/" + self.fileTrueName + "/manifest.json", "r") as f:
                BasicINfo = json.load(f)
            debug.dp(BasicINfo)
            return dict(BasicINfo)

    def manifestJsonDetector(self, testfor: dict) -> bool:
        """
        检验manifest.json
        A function to detect the manifest.json.
        :param testfor:输入informationJsonGetter函数的返回值
        put in informationJsonGetter function's return
        :return:如果有问题，会报错；没问题，会返回一个True
        if there are some problems, function will raise an Error;
        if not, function will return a "True" value.
        """
        keys = testfor.keys()
        if "fileDefinitionsInformation" in keys:
            fileDI = dict(testfor.get("fileDefinitionsInformation"))
            debug.dp(fileDI)
            debug.dp(type(fileDI))
            fileType = fileDI.get("fileType", None)
            readV = fileDI.get("fileReaderProgramVersion", None)
            if fileType is None:
                raise exceptions.FormatDeclarationError
            if fileType != "audio_score_transform_format":
                raise exceptions.FileError("This is not a ASTF file")
            if readV is None:
                raise exceptions.FormatDeclarationError
            elif readV != debug.versionStr:
                raise exceptions.VersionMismatchError("Program does not match format version")
            if 'fileName' in keys:
                if testfor.get("fileName") == self.fileTrueName:
                    self.isManifestNoBug = True
                    return True
                else:
                    raise exceptions.FormatDeclarationError
            else:
                raise exceptions.FormatDeclarationError
        else:
            raise exceptions.FormatDeclarationError

    def SettingsGetter(self):
        try:
            with open("./tmp/" + self.fileTrueName + "/information.json", "r") as f:
                information = json.load(f)
        except UnicodeDecodeError:
            with open("./tmp/" + self.fileTrueName + "/information.json", "r",
                      encoding="utf-8") as f:
                information = json.load(f)
        finally:
            debug.dp(information)

        try:
            with open("./tmp/" + self.fileTrueName + "/tracksDefinition.json", "r") as f:
                tracksDefinition = json.load(f)
        except UnicodeDecodeError:
            with open("./tmp/" + self.fileTrueName + "/tracksDefinition.json", "r",
                      encoding="utf-8") as f:
                tracksDefinition = json.load(f)
        finally:
            debug.dp(tracksDefinition)

        try:
            with open("./tmp/" + self.fileTrueName + "/soundsDefinition.json", "r") as f:
                soundsDefinition = json.load(f)
        except UnicodeDecodeError:
            with open("./tmp/" + self.fileTrueName + "/soundsDefinition.json", "r",
                      encoding="utf-8") as f:
                soundsDefinition = json.load(f)
        finally:
            debug.dp(soundsDefinition)

        tree = ElementTree(file="./tmp/" + self.fileTrueName + "/score_paperDef.xml")
        for ele in tree.iter():
            self.scoreStyle.append(ele)

        self.soundsDef["soundsDef"] = soundsDefinition
        self.tracksDef["tracksDef"] = tracksDefinition
        self.detailInformation["information"] = information

    def SettingsSaver(self):
        with open("./tmp/" + self.fileTrueName + "/information.json", "w", encoding="utf-8") as w:
            json.dump(self.detailInformation["information"], w)

        with open("./tmp/" + self.fileTrueName + "/tracksDefinition.json", "w", encoding="utf-8") as w:
            json.dump(self.tracksDef["tracksDef"], w)

        with open("./tmp/" + self.fileTrueName + "/soundsDefinition.json", "w", encoding="utf-8") as w:
            json.dump(self.soundsDef["soundsDef"], w)

        with open("./tmp/" + self.fileTrueName + "/score_paperDef.xml", "w", encoding="utf-8") as w:
            w.write(
                """<?xml version="1.0" encoding="utf-8" ?>
        <Style>
              <pageWidth>xxx1</pageWidth>
              <pageHeight>xxx2</pageHeight>
              <pagePrintableWidth>xxx3</pagePrintableWidth>
              <pageEvenLeftMargin>xxx4</pageEvenLeftMargin>
              <enableVerticalSpread>xxx5</enableVerticalSpread>
              <Spatium>xxx6</Spatium>
              </Style>""".replace("xxx1", self.scoreStyle[1].text)
                         .replace("xxx2", self.scoreStyle[2].text)
                         .replace("xxx3", self.scoreStyle[3].text)
                         .replace("xxx4", self.scoreStyle[4].text)
                         .replace("xxx5", self.scoreStyle[5].text)
                         .replace("xxx6", self.scoreStyle[6].text))


if __name__ == '__main__':
    compress("Test", "Test.astf", "test")
    test = ASTF_Format("Test.astf")
    # test.Basic_informationGetter()

# if __name__ == '__main__':
#     compress("Test", "Test.astf", "test")


# Reference documents:

# ————————————————————————————————————————————————
# for dir_path, dir_names, file_names in os.walk(file):
#     for filename in file_names:
#         # print(filename)
#         fpath = dir_path.replace(file, '')
#         file_path = os.path.join(dir_path, filename)
#         filename = os.path.join(fpath, filename)
#         print(file_path)
#         print(os.path.abspath(file_path))
#         # np = os.path.abspath(file_path)
#         # zf.write(file_path, arcname=filename)
#         zf.write(file_path, arcname=filename)
# ————————————————
# 版权声明：本文为CSDN博主「qq_35404578」的原创文章，遵循CC
# 4.0
# BY - SA版权协议，转载请附上原文出处链接及本声明。
# 原文链接：https: // blog.csdn.net / qq_35404578 / article / details / 116459174

# 以上为遍历文件夹的代码，遍历时会有bug，暂时不用

# https://huaweicloud.blog.csdn.net/article/details/107077671
# writeall()方法，可以将文件夹及以下所有内容写入到7z文件中，参考这个非常方便。

# https://blog.csdn.net/qq_49150070/article/details/123138799
